// VideoCameraStream.cpp: implementation of the CVideoCameraStream class.
//
//////////////////////////////////////////////////////////////////////

#pragma warning(disable:4786)

#include "stdafx.h"
#include "RMSTranslator.h"
#include "Camera.h"
#include "../RMSServer/RMSBaseServer.h"
#include "../Common/RMSLog.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
#define MAX_CONNECT_RETRY 0x10

CVideoCameraStream::CVideoCameraStream(CBaseServer *p)
{
	pBaseServer=p;
	m_bActive = FALSE;
	m_tcpSocket=0;
	m_pOwner = 0;
	
	m_pData = new BYTE[0x200000];
	m_bSupportRing=0; // support ring

}

CVideoCameraStream::~CVideoCameraStream()
{
	if(m_bActive){
		Terminate(TRUE);
	}

	if(m_pData) delete m_pData;
	m_pData=0;
}

DWORD CVideoCameraStream::OnTerminate()
{
	if(m_tcpSocket)
	{
		//shutdown(m_tcpSocket,SD_BOTH);
		shutdown(m_tcpSocket,SD_RECEIVE);// to avoid TIME_WAIT
		closesocket(m_tcpSocket);
		m_tcpSocket=0;
	}
	return DEFAULT_TERMINATION_TIMEOUT;
}

DWORD CVideoCameraStream::Terminate(BOOL bCritical)
{
	DWORD dwResult = CRMSThread::Terminate(bCritical);
	pBaseServer->DeleteCamera(inet_ntoa(m_saCamera.sin_addr));
	return dwResult;
}

BOOL CVideoCameraStream::Execute()
{
	return CRMSThread::Execute();
}

DWORD CVideoCameraStream::ThreadFunc()
{
	TRACE1("%x - CVideoCameraStream::ThreadFunc() - started\n", this);
	DWORD colTags, dwLen, dwTag;
	LINGER linger = { 1,0 };
	int i, retries;
	int err;
	bool binded;
	static int sin_port=0;
	BOOL bReuse=TRUE;// reset port/address
	
	if (sin_port==0) sin_port=htons(pBaseServer->BasePort+250);

	if ((ntohs(sin_port)-pBaseServer->BasePort)>300) {
		sin_port=htons(pBaseServer->BasePort);
	}
	Write2Log2("CVideoCameraStream ID=",m_pOwner->GetID());

open_connection:
	if (m_tcpSocket) {
		//shutdown(m_tcpSocket,SD_BOTH);
		shutdown(m_tcpSocket,SD_RECEIVE);// to avoid TIME_WAIT, reuse is required
		closesocket(m_tcpSocket);
		m_tcpSocket=0;
	}
	if (m_pOwner) m_pOwner->lastReceivedVideoTime=timeGetTime();
	if((m_tcpSocket = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) == INVALID_SOCKET)
	{
		TRACE0("Unable to create socket.\n");
		goto exit;
	}
	//Fixes Bug in MFC Socket Impl
	setsockopt(m_tcpSocket,SOL_SOCKET,SO_LINGER,(LPCTSTR)&linger,sizeof(linger));
//	setsockopt(m_tcpSocket, SOL_SOCKET, SO_REUSEADDR,(LPCTSTR)&bReuse,sizeof(BOOL));

	memset (&m_saServer, 0, sizeof(SOCKADDR_IN)); 
	m_saServer.sin_family = PF_INET;
	m_saServer.sin_port = sin_port;
	m_saServer.sin_addr.s_addr = inet_addr(m_pOwner->GetIP());
	
	// find and bin local port to bind to
	binded = false;
	for (i=0;i<100;i++) {
		if (m_bQuit)
		{
			goto exit;
		}
		if(bind(m_tcpSocket,(sockaddr*)&m_saServer,sizeof(SOCKADDR_IN)) != INVALID_SOCKET) 
		{
			binded = true;
			break;
		}
		else
		{
			m_saServer.sin_port=htons(ntohs(m_saServer.sin_port)+1);
		}
	}
	Write2Log2("Video cam: m_saServer.sin_port =",ntohs(m_saServer.sin_port));
	Write2Log1("Video cam: m_saServer.sin_addr.s_addr=",inet_ntoa(m_saServer.sin_addr));

	if(!binded)
	{
		TRACE0("Unable to bind socket.\n");
		Write2Log1("Video cam Unable to bind socket","");
		goto exit;
	}
	sin_port=htons(ntohs(m_saServer.sin_port)+1);//get next to rotate ports

	err = 0;
	for(retries = 0; retries < MAX_CONNECT_RETRY; retries++)
	{
		if (m_bQuit)
		{
			goto exit;
		}
		if((err = connect(m_tcpSocket,(sockaddr*)&m_saCamera,sizeof(SOCKADDR_IN))) != SOCKET_ERROR)
		{
			break;
		}
	}
	if(err == SOCKET_ERROR)
	{
		if (m_bQuit) 
		{
		goto exit;
		}
		Write2Log2("Vidcam: Unable to connect to server, m_bQuit=",m_bQuit);
		TRACE0("Unable to connect to server.\n");
		goto exit;
	}

	{ // wait for sendability
		fd_set fdSet;
		FD_ZERO(&fdSet);
		FD_SET(m_tcpSocket, &fdSet);
		timeval tv = { 0, 500 };
		int sr = select(1, NULL, &fdSet, NULL, &tv);
		if( sr == 0 || sr == SOCKET_ERROR )
		{
			TRACE1("CVideoCameraStream::ThreadFunc(), select()=%d\n", WSAGetLastError());
		}
	}

	Write2Log1("Connected ","OK");

	err = tcpRequestFormat();
	if (m_pOwner) m_pOwner->lastReceivedVideoTime=timeGetTime();
	if(err != 1)
	{
		if(failure) { SetEvent(failure); failure = success = NULL; }

		if(m_bQuit)		goto exit;
		if(err == 0)	goto exit;
		else {
			Sleep(100);
			goto open_connection;
		}
	}

	if(success) { SetEvent(success); failure = success = NULL; }

	if(m_nUDPCameraPort)
	{
		//	Start wave session
		SOCKADDR_IN saWaveCamera;
		memset (&saWaveCamera, 0, sizeof(SOCKADDR_IN));
		saWaveCamera.sin_family = PF_INET;
		saWaveCamera.sin_addr.s_addr = m_saCamera.sin_addr.s_addr;
		saWaveCamera.sin_port = htons(m_nUDPCameraPort);

		if(m_pOwner)
		{
			m_pOwner->m_waveStream.Init(saWaveCamera, m_pOwner);
		}
	}

	if(!tcpTAGRequest(m_tcpSocket))
	{
		if(m_bQuit)	goto exit;else	goto open_connection;
	}

	while(m_bQuit==0)
	{
		if(!tcpTAGRequest(m_tcpSocket)){
			if(m_bQuit)	goto exit;else	goto open_connection;
		}
		colTags = 0;
		if (tcpReceiveData(m_tcpSocket, m_bQuit, &colTags, sizeof(DWORD)) < 0) {
					if(m_bQuit)	goto exit;else	goto open_connection;
		}
		if (colTags==0xBADF) {
			pBaseServer->SetMode_NewPassword(m_pOwner->GetID());
			goto exit;
		}			
		if (m_pOwner) m_pOwner->lastReceivedVideoTime=timeGetTime();

		while(colTags)
		{
			dwTag = 0;
			if(tcpReceiveData(m_tcpSocket, m_bQuit, &dwTag, sizeof(DWORD))<0){
					if(m_bQuit)	goto exit;else	goto open_connection;
			}
			if (m_pOwner) m_pOwner->lastReceivedVideoTime=timeGetTime();// update time
			
			switch(dwTag)
			{
			case TAG_TIMEMARK:
				if(tcpReceiveData(m_tcpSocket, m_bQuit, m_pData,sizeof(TimeMark))<0) {
					if(m_bQuit)	goto exit;else	goto open_connection;
				}
				if (m_pOwner) m_pOwner->lastReceivedVideoTime=timeGetTime();// update time
				break;
			case TAG_RMSVIDEO_v01:
			case TAG_RMSVIDEO_v02:
			{
				int ID=m_pOwner->GetID();
				
				dwLen = 0;
				if(tcpReceiveData(m_tcpSocket, m_bQuit, &dwLen, sizeof(DWORD))<0){
					if(m_bQuit)	goto exit;else	goto open_connection;
				}

				if(tcpReceiveData(m_tcpSocket, m_bQuit, m_pData, dwLen)<0){
					if(m_bQuit)	goto exit;else	goto open_connection;
				}
				
				EnterCriticalSection(pBaseServer->GetDataPtr_CriticalData(ID));
				*pBaseServer->GetDataPtr_TypeVideo(ID)=dwTag;// put correct video type
				*pBaseServer->GetDataPtr_SizeVideo(ID)=dwLen;
				memcpy(pBaseServer->GetDataPtr_dtVideo(ID),m_pData,dwLen);
				LeaveCriticalSection(pBaseServer->GetDataPtr_CriticalData(ID));
			}
			break;

			case TAG_TIME_v01:
			{
				int ID=m_pOwner->GetID();
				
				dwLen = 0;
				if(tcpReceiveData(m_tcpSocket, m_bQuit, &dwLen, sizeof(DWORD))<0){
					if(m_bQuit)	goto exit;else	goto open_connection;
				}

				if(tcpReceiveData(m_tcpSocket, m_bQuit, m_pData, dwLen)<0) {
					if(m_bQuit)	goto exit;else	goto open_connection;
				}
				
				EnterCriticalSection(pBaseServer->GetDataPtr_CriticalData(ID));
				*pBaseServer->GetDataPtr_SizeTime(ID)=dwLen;
				memcpy(pBaseServer->GetDataPtr_dtTime(ID),m_pData,dwLen);
				LeaveCriticalSection(pBaseServer->GetDataPtr_CriticalData(ID));
			}
				break;
			case TAG_DATE_v01:
			{
				int ID=m_pOwner->GetID();
				
				dwLen = 0;
				if(tcpReceiveData(m_tcpSocket, m_bQuit, &dwLen, sizeof(DWORD))<0){
					if(m_bQuit)	goto exit;else	goto open_connection;
				}

				if(tcpReceiveData(m_tcpSocket, m_bQuit, m_pData, dwLen)<0) {
					if(m_bQuit)	goto exit;else	goto open_connection;
				}
				
				EnterCriticalSection(pBaseServer->GetDataPtr_CriticalData(ID));
				*pBaseServer->GetDataPtr_SizeDate(ID)=dwLen;
				memcpy(pBaseServer->GetDataPtr_dtDate(ID),m_pData,dwLen);
				LeaveCriticalSection(pBaseServer->GetDataPtr_CriticalData(ID));
			}
				break;
			default:
				dwLen = 0;
				if(tcpReceiveData(m_tcpSocket, m_bQuit, &dwLen, sizeof(DWORD))<0){
					if(m_bQuit)	goto exit;else	goto open_connection;
				}

				if(tcpReceiveData(m_tcpSocket, m_bQuit, m_pData, dwLen)<0){
					if(m_bQuit)	goto exit;else	goto open_connection;
				}
			}
			--colTags;
		}
	}

exit:
	//shutdown(m_tcpSocket,SD_BOTH);
	shutdown(m_tcpSocket,SD_RECEIVE);// to avoid TIME_WAIT
	closesocket(m_tcpSocket);
	m_tcpSocket=0;
	pBaseServer->DeleteCamera(inet_ntoa(m_saCamera.sin_addr));

TRACE1("%x - CVideoCameraStream::ThreadFunc() - finished\n", this);
	return 0;
}

BOOL CVideoCameraStream::Init(SOCKADDR_IN saVideo, LPCLiveCamera pOwner)
{
	m_saCamera = saVideo;
	m_pOwner = pOwner;

	Execute();
	m_bActive = TRUE;
	m_bNoVideoEnabled = FALSE;

	failure = NULL;
	success = NULL;
	m_bEnableRing = FALSE;
	memset(m_szCaller, 0, sizeof(m_szCaller));
	return TRUE;
}

void CVideoCameraStream::SetCaller(char* pCaller, BOOL bEnableRing)
{
	if(pCaller && (*pCaller != 0))
	{
		strncpy(m_szCaller, pCaller, sizeof(m_szCaller)-1);
	}
	m_bEnableRing = bEnableRing;
}

void CVideoCameraStream::SetEventHandlers(HANDLE h1, HANDLE h2)
{
	if(h1 && h2)
	{
		failure = h1;
		success = h2;
	}
}

void CVideoCameraStream::EnableNoVideo(BOOL bEnable)
{
	m_bNoVideoEnabled = bEnable;
}

BOOL CVideoCameraStream::Restart()
{
	Execute();
	m_bActive = TRUE;
	pBaseServer->AddCamera(static_cast<LPCTSTR>(inet_ntoa(m_saCamera.sin_addr)));
	return TRUE;
}

BOOL CVideoCameraStream::Stop()
{
	m_bActive = FALSE;

	Terminate();
	TRACE2("%x: CVideoCameraStream::Stop(%s)\n", this, inet_ntoa(m_saCamera.sin_addr));
	pBaseServer->DeleteCamera(static_cast<LPCTSTR>(inet_ntoa(m_saCamera.sin_addr)));
	return TRUE;
}

BOOL CVideoCameraStream::IsActive() const
{
	return m_bActive;
}

DWORD CVideoCameraStream::tcpRing(DWORD* ring)
{
	// send ring
	if (m_bSupportRing && ring[0]==TAG_VC_RING && m_tcpSocket && ring[1]<1000) {

		if(send(m_tcpSocket,(char*)ring,ring[1]+4,0) == SOCKET_ERROR)
		{
			return (DWORD)-1;
		}
	}
	return 0;
}
DWORD CVideoCameraStream::tcpRequestFormat()
{
	DWORD data[1256];
	DWORD* Request;
	DWORD dwLen;
	DWORD dwTag;

	CInfo info;
	if(!(m_pOwner->pBaseServer->GetCameraInfo(m_pOwner->GetID(), info)))
	{
		return (DWORD)-1;
	}
//---------------
		Request = (DWORD*)data;
		DWORD* requestStart=Request;
		int	  tagCount=0;
		int nLenPass=strlen(info.m_strPassword)+1;
		dwLen = sizeof(DWORD)*15 + nLenPass;
		*Request++ = TAG_REQUEST;
		*Request++ = 6;
		*Request++ = TAG_RMSVIDEO_v01;tagCount++;
		*Request++ = m_bNoVideoEnabled ? 1 : 0;
		*Request++ = TAG_TIME_v01;tagCount++;
		*Request++ = 0;
		*Request++ = TAG_DATE_v01;tagCount++;
		*Request++ = 0;
		*Request++ = TAG_RMSSOUND_v01;tagCount++;
		*Request++ = 0;
		*Request++ = TAG_CAMERA_v01;tagCount++;
		*Request++ = sizeof(DWORD);
		*Request++ = m_pOwner->GetID();		
		Write2Log2("Video cam, id sent=",m_pOwner->GetID());
		// ---- ring -- ring and provide IP-info of the caller

		// ---- send - support ring to the encoder
		*Request++ = TAG_SUPPORT_RING;tagCount++;
		*Request++ = (DWORD)m_bEnableRing;
		int rsize = strlen(m_szCaller);
		if(rsize == 0)
		{
			unsigned short port = ntohs(m_saServer.sin_port);
			sprintf(m_szCaller, "server server server server %i %s %s %i %i %i %i %i %i 0",
					info.m_iID, info.m_strPassword, inet_ntoa(m_saServer.sin_addr), port,
					info.m_iFrameRate, info.m_iQualityRange, info.m_iBitrate, info.m_iWidth, info.m_iHeight);
		}
		rsize = strlen(m_szCaller);
		*Request++ = TAG_VC_RING_1;tagCount++;
		rsize = (rsize+3)>>2<<2;
		*Request++ = rsize;
		memcpy(Request, m_szCaller, rsize);
		Request += rsize>>2;

//#ifndef _RMS_TRANSLATOR_
		*Request++ = TAG_PASSWORD_v01;tagCount++;
		*Request++ = nLenPass;
		strcpy(reinterpret_cast<char*>(Request), info.m_strPassword);
		// ------ here is bug, we should send password, supplied by user.

		Write2Log1("Video cam, password sent=",info.m_strPassword);
//#else
//		nLenPass = 0;
//#endif
		requestStart[1]=tagCount;
		dwLen=(Request-requestStart)*sizeof(DWORD)+nLenPass;
		Request = data;	
		m_nUDPCameraPort = 0;
		
//---------------
/*
	dwLen = sizeof(DWORD)*15 + strlen(info.m_strPassword) + 1;
	Request = data;
	*Request++ = TAG_REQUEST;
	*Request++ = 6;

	*Request++ = TAG_RMSVIDEO_v01;
	*Request++ = 0;
	*Request++ = TAG_TIME_v01;
	*Request++ = 0;
	*Request++ = TAG_DATE_v01;
	*Request++ = 0;
	*Request++ = TAG_RMSSOUND_v01;
	*Request++ = 0;
	*Request++ = TAG_CAMERA_v01;
	*Request++ = sizeof(DWORD);
	*Request++ = m_pOwner->GetID();
	*Request++ = TAG_PASSWORD_v01;
	*Request++ = strlen(info.m_strPassword) + 1;
	strcpy(reinterpret_cast<char*>(Request), info.m_strPassword);
	Request = data;
	
	m_nUDPCameraPort = 0;
*/		
	if(send(m_tcpSocket,(char*)Request,dwLen,0) == SOCKET_ERROR)
	{
		return (DWORD)-1;
	}

	dwTag = TAG_CONFWAIT;
	while(dwTag == TAG_CONFWAIT)
	{
		if(tcpReceiveData(m_tcpSocket, m_bQuit, &dwTag, sizeof(DWORD)) < 0)
		{
			return (DWORD)-1;
		}
	}

	if(dwTag != TAG_ANSWER)
	{
		return (DWORD)-1;
	}

	if(tcpReceiveData(m_tcpSocket, m_bQuit, &dwTag, sizeof(DWORD))<0) return (DWORD)-1;
	if (m_pOwner) m_pOwner->lastReceivedVideoTime=timeGetTime();// update time

	// check if it's new server or encoder with ring support
	if (dwTag==TAG_SUPPORT_RING) {
			m_bSupportRing=TRUE;
			if(tcpReceiveData(m_tcpSocket, m_bQuit, &dwTag, sizeof(DWORD))<0) return (DWORD)-1;
	}
	if(dwTag == TAG_GOODFORMAT)
	{
		return (DWORD)1;
	}
	else 		
	if(dwTag == TAG_SOUNDUDPPORT)
	{
		if(tcpReceiveData(m_tcpSocket, m_bQuit, &m_nUDPCameraPort, sizeof(int))<0)
		{
			return (DWORD)-1;
		}

		if(tcpReceiveData(m_tcpSocket, m_bQuit, &dwTag, sizeof(DWORD))<0)
		{
			return (DWORD)-1;
		}
		if(dwTag == TAG_GOODFORMAT)
		{
			return (DWORD)1;
		}
		else
		{
			char ch = 0;
			if(send(m_tcpSocket,&ch,sizeof(char),0) == SOCKET_ERROR)
			{
				return (DWORD)-1;
			}
		}
	}
	else
	{
		char ch = 0;
		if(send(m_tcpSocket,&ch,sizeof(char),0) == SOCKET_ERROR)
		{
			return (DWORD)-1;
		}
	}
	return (DWORD)0;
}

BOOL CVideoCameraStream::CloseProcess()
{
	CRMSThread::Terminate(1);
	return TRUE;
}

int CVideoCameraStream::tcpReceiveData(SOCKET& Socket,BOOL& bTerminated, void* data, int Len)
{
	int i=0;
	while (i<Len) {
		if(bTerminated) return -1;
		int n = recv(Socket,(char*)data+i, Len-i, 0);
		if (n == SOCKET_ERROR) {
				return -1;
		}
		if (n == 0) {
 				return -1;
		}
		i+=n;
	}
	return i;
}

BOOL CVideoCameraStream::tcpTAGRequest(SOCKET& Socket)
{
	int n = 0;
	DWORD dwTag = TAG_GETDATA;

	if((n = send(Socket,(char*)&dwTag,4,0)) == SOCKET_ERROR)
	{
		return FALSE;
	}
	if(n != 4)
	{
		return FALSE;
	}
	return TRUE;
}
